<%#
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
Licensed to the public under the Apache License 2.0.
-%>
<%

local loragw = require "luci.model.loragw".init()

if luci.http.formvalue("status") == "1" then

	if loragw == nil then
		luci.http.prepare_content("application/json")
		luci.http.write("[]");
		return;
	end

	loragw:set("logread")
	local res=loragw:get()
	loragw:Close()

	luci.http.prepare_content("application/json")
	luci.http.write(res);
	return
end

if luci.http.formvalue("status") == "2" then
	if loragw == nil then
		luci.http.prepare_content("application/json")
		luci.http.write("[]");
		return;
	end
	loragw:set("dutycycle")
	local dutycycle=loragw:get()
	loragw:Close()
	luci.http.prepare_content("application/json")
	luci.http.write(dutycycle);
	return
end

local statInfo = "{}"
if loragw ~= nil then
	loragw:set("dutycycle")
	statInfo=loragw:get();
	loragw:Close()
end
%>
<%+header%>
<style type="text/css">

</style>
<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
<script type="text/javascript" src="<%=resource%>/js/heatmap.js"></script>
<script type="text/javascript">
	var LGW_STAT_INFO = <%=statInfo%>;
	var FREQ_MIN;
	if(LGW_STAT_INFO){
		console.log(LGW_STAT_INFO.ChanUsage);
		FREQ_MIN = LGW_STAT_INFO.ChanUsage[0].freq;
	}
</script>
<script type="text/javascript">
	var H337Data = {
		max : 120,
		min : 0,
		data : []
	};
	var ChanNB = 16;
	var TimeWidth = 60;
	var H337Height = 400;
	var FreqStart = FREQ_MIN ? FREQ_MIN - 100000 : 470300000;
	var h337DataOrg = [];
	var h337BasePointNb = 40;
	var h337Bw125Point = h337BasePointNb + 10;
	var H337Instance;
	var DutyCycleHeatMapIns;
	var h337_t = 0;
	var Blk1 = ChanNB*200000 / 640; /* 每个像素宽度代表的freq宽度 */
	var h337Radius = 200000 / Blk1 / h337BasePointNb / 2 ;
	var h337tmstep = H337Height / h337Radius / 2;
	var h337tmms = (TimeWidth * 1000) / h337tmstep;
	var h337ldc = 0.75;
	console.log( "Radius", h337Radius);
	console.log("tmms", h337tmms);

	var DutyCycleHeatMapData = {
		max : TimeWidth*500,
		min : 0,
		data : []
	};

	function H337Push(time, freq, rssi, bw, airtime){
		h337DataOrg.push([time, freq, rssi, bw, airtime]);
		//H337Instance.setData(H337Data);				
	}
	function H337DataGenerate(data, curTime){
		var time = data[0];
		var freq = data[1];
		var rssi = data[2];
		var bw = data[3];
		var airtime = data[4];

		var center =( freq - FreqStart ) / Blk1;

		if( curTime < time ){
			console.log("#####", curTime, time, freq, rssi, bw, airtime);
			return;
		}
		if( (curTime - time) > (TimeWidth * 1000) ){
			console.log("######",curTime, time, freq, rssi, bw, airtime);
			return;
		}
		var baseY = H337Height - (( curTime - time ) / h337tmms)*h337Radius*2;
		
		var val = rssi + 140;
		var width = bw / Blk1; /* 占用的宽度 */
		var h337PointNb = h337Bw125Point;
		switch(bw){
			case 125000 : h337PointNb = h337Bw125Point;break;
			case 250000 : h337PointNb = h337Bw125Point*2;break;
			case 500000 : h337PointNb = h337Bw125Point*4;break;
		}
		
		for(var i = 0; i <=h337PointNb; i++){
			var point = {
				x : center + h337Radius*i,
				y : baseY,
				value : val - ((i > h337PointNb/3) ? (val * (i - h337PointNb/3)  / h337PointNb / h337ldc) : 0)
			}
			 
			H337Data.data.push(point);
			if( i ){
				var point = {
					x : center - h337Radius*i,
					y : baseY,
					//value : val - val * i / h337BasePointNb / h337ldc
					value : val - ((i > h337PointNb/3) ? (val * (i - h337PointNb/3)  / h337PointNb / h337ldc) : 0)

				}

				H337Data.data.push(point);
			}
			
			
			var YHeight = Math.round(airtime / h337tmms);
			for( var j = 1; j < YHeight; j++ ){
				var point = {
					x : center + h337Radius*i,
					y : baseY + h337Radius*j*2,
					value : val - ((i > h337PointNb/3) ? (val * (i - h337PointNb/3)   / h337PointNb / h337ldc) : 0)
				}

				H337Data.data.push(point);
				if( i ){
					var point = {
						x : center - h337Radius*i,
						y : baseY+ h337Radius*j*2,
						value : val - ((i > h337PointNb/3) ? (val * (i - h337PointNb/3)  / h337PointNb / h337ldc) : 0)
					}
			
					H337Data.data.push(point);
				}
				
			}
		}
	}
	function DutyCycleHeatMapDataGen(data){
		var time = data[0];
		var freq = data[1];
		var rssi = data[2];
		var bw = data[3];
		var airtime = data[4];

		var center =( freq - FreqStart ) / Blk1;

		var point = {
			x : center,
			y : 10,
			value : airtime
		}
		DutyCycleHeatMapIns.addData(point);
		var h337PointNb = 3;
		switch(bw){
			case 125000:h337PointNb=3;break;
			case 250000:h337PointNb=6;break;
			case 500000:h337PointNb=12;break;
		}

		for( var i = 1; i <= h337PointNb; i++){
			var point1 = {
				x : center + i*5,
				y : 10,
				value : airtime
			}
			DutyCycleHeatMapIns.addData(point1);
			var point2 = {
				x : center - i*5,
				y : 10,
				value : airtime
			}
			DutyCycleHeatMapIns.addData(point2);
		}
		
	}
	function H337Refresh(T){

		H337Data.data = [];
		DutyCycleHeatMapData.data = [];
		DutyCycleHeatMapIns.setData(DutyCycleHeatMapData);
		var DutyData = {};

		for( var i = 0; i < h337DataOrg.length; i ++ ){
			if( (T > h337DataOrg[i][0]) && (T - h337DataOrg[i][0]) > TimeWidth*1000 ){
				console.log("####", h337DataOrg[i][0]);
				h337DataOrg.splice(i,1);
			}
			else{
				H337DataGenerate(h337DataOrg[i],T);
				var itemFreq = h337DataOrg[i][1];
				var itemBw = h337DataOrg[i][3];
				var itemAirTime = h337DataOrg[i][4];
				if( itemBw == 125000){					
					if( DutyData["" + h337DataOrg[i][1]] == undefined){
						DutyData["" + h337DataOrg[i][1]] = itemAirTime;
					}
					else{
						DutyData["" + h337DataOrg[i][1]] += itemAirTime;
					}
				}
				
				if( itemBw == 250000){
					
					if( DutyData["" + (itemFreq + 100000)] == undefined){
						DutyData["" + (itemFreq + 100000)] = itemAirTime;
					}
					else{
						DutyData["" + (itemFreq + 100000)] += itemAirTime;
					}
					if( DutyData["" + (itemFreq - 100000)] == undefined){
						DutyData["" + (itemFreq - 100000)] = itemAirTime;
					}
					else{
						DutyData["" + (itemFreq - 100000)] += itemAirTime;
					}

				}
				if(itemBw == 500000){
					if( DutyData["" + (itemFreq + 100000)] == undefined){
						DutyData["" + (itemFreq + 100000)] = itemAirTime;
					}
					else{
						DutyData["" + (itemFreq + 100000)] += itemAirTime;
					}
					if( DutyData["" + (itemFreq - 100000)] == undefined){
						DutyData["" + (itemFreq - 100000)] = itemAirTime;
					}
					else{
						DutyData["" + (itemFreq - 100000)] += itemAirTime;
					}
					if( DutyData["" + (itemFreq + 300000)] == undefined){
						DutyData["" + (itemFreq + 300000)] = itemAirTime;
					}
					else{
						DutyData["" + (itemFreq + 300000)] += itemAirTime;
					}
					if( DutyData["" + (itemFreq - 300000)] == undefined){
						DutyData["" + (itemFreq - 300000)] = itemAirTime;
					}
					else{
						DutyData["" + (itemFreq - 300000)] += itemAirTime;
					}
				}
			}
			
		}
		console.log("h337datalen",H337Data.data.length);
		H337Instance.setData(H337Data);
		for(var Dd in DutyData ){
			var freq = parseInt(Dd);
			var val = DutyData[Dd];
			var center =( freq - FreqStart ) / Blk1;

			var point = {
				x : center,
				y : 5,
				value : val
			}
			DutyCycleHeatMapData.data.push(point);
			
			for( var i = 1; i <= 3; i++){
				var point1 = {
					x : center + i*5,
					y : 5,
					value : val
				}
				DutyCycleHeatMapData.data.push(point1);
				var point2 = {
					x : center - i*5,
					y : 5,
					value : val
				}
				DutyCycleHeatMapData.data.push(point2);
			}
			
		}
		DutyCycleHeatMapIns.setData(DutyCycleHeatMapData);
		//console.log("DutyCycleHeatMapData",DutyCycleHeatMapData);
		
	}

	$(document).ready(function(){
		H337Instance = h337.create({
			container : document.getElementById('heatmap'),
			backgroundColor: '#333333',
			radius: h337Radius,
			maxOpacity: 1,
			minOpacity: .1,
			blur: .1/*,
			gradient : {
				'.1' : "blue",
				'.5' : "green",
				'.65' : "yellow",
				'.8' : "red",
				'.95' : "white"
			}
			*/
		});
		/*
		setInterval(function(){
			H337Refresh();
		}, 1000);
		*/

		DutyCycleHeatMapIns = h337.create({
			container : document.getElementById('heatmap-dtc'),
			backgroundColor: '#333333',
			radius: 20,
			maxOpacity: 1,
			minOpacity: .1,
			blur: .75/*,
			gradient : {
				'.05' : "blue",
				'.4' : "green",
				'.6' : "yellow",
				'.8' : "red"
			}	*/	
		});

		var xaisData = [];
		for( i = 0; i < ChanNB; i++ ){
			xaisData.push("" + ((FreqStart + 100000 + i*200000)/1000000));
		}
		var H337BdOption =  {
			backgroundColor : '#333333',
			grid : {
				left : 20,
				top : 0,
				bottom : 40,
				right : 20
			},
			xAxis: {
				type: 'category',
				data: xaisData,
				name : "Freq.(MHz)",
				axisLine : {
					lineStyle : {
						color: "#2eeb0c",
						width : 1
					}
				},
				axisLabel : {
					interval : 0
				}
			},
			yAxis: {
				type: 'value',
				axisLine : {
					lineStyle : {
						color: "#2eeb0c",
						width : 1
					}
				}
			}
		};
		var H337BdChart = echarts.init(document.getElementById("heatmap-bd"));
		H337BdChart.setOption(H337BdOption);

		var MoteMapChart = echarts.init(document.getElementById("moteMap"));
		

		function moteMapRefresh(){
			var MotexLineData = [];
			var MoteS1 = [];
			var MoteS2 = [];
			for( var item in MoteList){
				MotexLineData.push(item);
				MoteS1.push(MoteList[item].packet);
				MoteS2.push(MoteList[item].airtime/1000);
			}
			//console.log(MoteS1);
			var moteMapOption = {
				color : ['#2eeb0c', 'yellow'],
				backgroundColor : '#333333',
				grid : {
					left : 40,
					top : 40,
					bottom : 50,
					right : 40
				},
				tooltip : {
					show : true
				},
				xAxis: {
					type: 'category',
					data: MotexLineData,
					name : 'DevAddr',
					axisLine : {
						lineStyle : {
							color: "#2eeb0c",
							width : 1
						}
					},
					axisLabel : {
						interval : 0,
						rotate : 45
					}
				},
				yAxis: [{
					type: 'value',
					name : "AirTime(s)",
					axisLine : {
						lineStyle : {
							color: "#2eeb0c",
							width : 1
						}
					},
					splitLine :{
						show : false
					}
				},{
					type: 'value',
					name : 'Packet',
					axisLine : {
						lineStyle : {
							color: "#2eeb0c",
							width : 1
						}
					},
					splitLine :{
						show : false
					}
				}],
				series: [{
					name: 'AirTime',
					type: 'bar',
					yAxisIndex : 0,
					data: MoteS2,
					barMaxWidth: 10
				},{
					name: 'Packet',
					type: 'bar',
					yAxisIndex : 1,
					data: MoteS1,
					barMaxWidth: 10
				}]
			};
			MoteMapChart.setOption(moteMapOption);
		}
		
		setInterval(function(){
			moteMapRefresh();
		}, 1000);
	});
</script>
<style type="text/css">
	#heatmap-bd {
		position: absolute;
	}
	#heatmap{
		position: absolute;
		left:20px;
	}
	#moteMap{
		position: absolute;
		top:0px;
		left:680px;
		background: #333333;
	}
	#heatmap-dt{
		background: red;
	    position: absolute;
	    top: 430px;
	    left: 20px;
	    height: 10px;
	    width: 640px;
	}
</style>
<script type="text/javascript">
	var MoteList = new Object();

	function timeOnAir(Mod, bw, bps, prea, cr, size){
		if( Mod == "LORA"){
			var SF = bps;
			var BW = bw;
			var CR = 1;
			switch(cr){
				case '4/5': CR = 1;break;
				case '4/6': CR = 2;break;
				case '4/8': CR = 2;break;
				case '4/7': CR = 2;break;
				default:break;
			}

			var Tsym = Math.pow(2,SF) / BW;
			var Tprea = (prea + 4.25)*Tsym;
			var H = 1;
			var DE = ( SF >= 11 ) ? 1 : 0;

			var plSynNb = 8 + (Math.ceil((8*size - 4*SF + 28 + 16 - 20*H)/(4*(SF - 2*DE)))*(CR  + 4));
			var Tpl = plSynNb * Tsym;

			return Math.round(Tprea + Tpl);
		}
		else{
			var Tfsk = (( 8 * (prea + 3 + 1 + size + 2)) / bps ) * 1000;
			return Tfsk + 1;
		}
	}
	Date.prototype.format = function(format) {
		var o = {
			"M+": this.getMonth() + 1, //month
			"d+": this.getDate(), //day
			"h+": this.getHours(), //hour
			"m+": this.getMinutes(), //minute
			"s+": this.getSeconds(), //second
			"q+": Math.floor((this.getMonth() + 3) / 3), //quarter
			"S": this.getMilliseconds() //millisecond
		}

		if (/(y+)/.test(format)) {
			format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
		}

		for (var k in o) {
			if (new RegExp("(" + k + ")").test(format)) {
				format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
			}
		}
		return format;
	}

	var MTYPE_JOIN_REQUEST     =     0;
	var MTYPE_JOIN_ACCEPT      =     1;
	var MTYPE_UNCONFIRM_DATA_UP=     2;
	var MTYPE_UNCONFIRM_DATA_DOWN=   3;
	var MTYPE_CONFIRM_DATA_UP  =     4;
	var MTYPE_CONFIRM_DATA_DOWN=     5;
	var MTYPE_RFU              =     6;
	var MTYPE_PROPRITARY       =     7;
	function pkt_type_toString(type){
		switch(type){
			case MTYPE_JOIN_REQUEST : return "Join Request";
			case MTYPE_JOIN_ACCEPT:return "Join Accept";
			case MTYPE_UNCONFIRM_DATA_UP:return "Uncomfirm data up";
			case MTYPE_UNCONFIRM_DATA_DOWN:return "Uncomfirm data down";
			case MTYPE_CONFIRM_DATA_UP:return "Confirm data up";
			case MTYPE_CONFIRM_DATA_DOWN:return "Confirm data down";
			case MTYPE_RFU:return "RFU";
			case MTYPE_PROPRITARY:return "Propritary";
			default:return "Unknown";
		}
	}
	var total = 0;
	var up = 0;
	var down = 0;

	var interval = null;
	

	function _base64ToArrayBuffer(base64){
		var binary_string =  window.atob(base64);
		var len = binary_string.length;
		var bytes = new Uint8Array( len );
		for (var i = 0; i < len; i++)        {
			bytes[i] = binary_string.charCodeAt(i);
		}
		return bytes.buffer;
	}
	var logger_idx = 0;
	function pktToggle(id){ 
		$("#" + id + "_d").toggle();
	}

	function MTypeToString(Mtype){
		switch(Mtype){
			case 0 : return "Join Request";
			case 1 : return "Join Accept";
			case 2 : return "Unconfirmed Data Up";
			case 3 : return "Unconfirmed Data Down";
			case 4 : return "Confirmed Data Up";
			case 5 : return "Confirmed Data Down";
			case 6 : return "ReJoin Request";
			case 7 : return "Proprietary";
		}
		return "";
	}
	function CRCInfo2String(info){
		var stat = info.stat;
		if(stat != undefined ){
			switch(stat){
				case 1 :return "CRC_OK";
				case -1 : return "CRC_ERR";
				case 0 : return "NO_CRC";
			}
		}
		else{
			if( info.ncrc ){
				return "NO_CRC";
			}
			else{
				return "CRC";
			}
		}
	}
	function logread(){
		var content = $("#packet_logger_div");
		XHR.get('<%=REQUEST_URI%>', { status: 1 },
			function(x, response){
				var currenttmms = response.time;
				var infos = response.packets;

				var time = new Date();
				infos.forEach(function(info,i){
					var fType = 0;
					
					fType = info.fType;
					delete info.fType;
					
					total++;
					var pkt_id = "pkt_" + (logger_idx++);
					content.prepend("<div id='"+pkt_id+"' class='pkt_item' > \
						<div class='pkt_l fType_"+fType+"' id='"+pkt_id+"_l' onClick='pktToggle(\""+pkt_id+"\")' ></div>		\
						<div class='pkt_d' id='"+pkt_id+"_d'></div>		\
						</div>");
					var pkt_l = $("#" + pkt_id+"_l");
					pkt_l.append("\
						<div class='tm'>"+time.format("hh:mm:ss")+"</div>\
						<div class='freq'>"+(info.freq / 1000000)+"</div>\
						<div class='rssi'>-</div>	\
						<div class='snr'>-</div>	\
						<div class='txpwr'>-</div> \
						<div class='stat'>"+CRCInfo2String(info)+"</div>\
						<div class='modu'>"+(info.modu?info.modu:"&nbsp;&nbsp;&nbsp;")+"</div>\
						<div class='codr'>"+(info.codr?info.codr:"&nbsp;&nbsp;&nbsp;")+"</div>\
						<div class='datr'>"+(info.datr?info.datr:"")+"</div>\
						<div class='cnt'>-</div>\
						<div class='airtime'>-</div>\
						<div class='devaddr'>-</div>\
						<div class='fport'>-</div>\
						<div class='plsize'>-</div>\
						<div class='cmd'>-</div>\
						");

					$("#"+ pkt_id+"_d").append("<pre>" + JSON.stringify(info, null, 4)+"</pre>");

					if( info.stat && info.stat == -1 ){
						pkt_l.prepend("<div class='dir' ><i style='color:#0069d6' class='fa fa-sort-up'></i></div>");
						pkt_l.children(".rssi").first().html(info.rssi);
						pkt_l.children(".snr").first().html(info.lsnr?info.lsnr:"");
					}
					else {
						if(info.modu == "LORA"){
							var sfi = info.datr.search("SF");
							var bwi = info.datr.search("BW");
							var airTime = 0;
							if( sfi >= 0 && bwi >= 0 ){
								var infSf = parseInt(info.datr.substring(sfi + 2, bwi));
								var infBw = parseInt(info.datr.substring(bwi + 2));
								airTime = timeOnAir(info.modu, infBw, infSf, 8, info.codr, info.size);
								pkt_l.children(".airtime").first().html(airTime);
							}
							if( info.rssi && info.utmms ){
								try{
									H337Push(info.utmms, info.freq, info.rssi, infBw*1000, airTime);
								}
								catch(e){
									console.log(e);
								}
							}
						}
						var buffer = _base64ToArrayBuffer(info.data);
						var PHYPayload = new DataView(buffer);
						var MHDRObj = {};

						var MHDR = PHYPayload.getUint8(0);

						var MType = ( MHDR & 0xE0 ) >> 5;
						switch(MType){
							case 0:{
								pkt_l.prepend("<div class='dir' data='"+MTypeToString(MType)+"' ><i style='color:#f7d429' class='fa fa-bolt'></i></div>");
								pkt_l.addClass("msg_jr");
								up++;
								break;
							}
							case 1:{
								pkt_l.prepend("<div class='dir' data='"+MTypeToString(MType)+"' ><i style='color:#07ec7a' class='fa fa-bolt'></i></div>");
								pkt_l.addClass("msg_ja");
								down++;
								break;
							}
							case 2:{
								pkt_l.prepend("<div class='dir' data='"+MTypeToString(MType)+"' ><i style='color:#0069d6' class='fa fa-sort-up'></i></div>");
								pkt_l.addClass("msg_ucu");
								up++;
								break;
							}
							case 4:{
								pkt_l.prepend("<div class='dir' data='"+MTypeToString(MType)+"' ><i style='color:#0069d6' class='fa fa-sort-up'></i></div>");
								pkt_l.addClass("msg_cu");
								up++;
								break;
							}
							case 3:{
								pkt_l.prepend("<div class='dir' data='"+MTypeToString(MType)+"' ><i style='color:#0069d6' class='fa fa-sort-down'></i></div>");
								pkt_l.addClass("msg_ucd");
								down++;
								break;
							}
							case 5:{
								pkt_l.prepend("<div class='dir' data='"+MTypeToString(MType)+"' ><i style='color:#0069d6' class='fa fa-sort-down'></i></div>");
								pkt_l.addClass("msg_cd");
								down++;
								break;
							}
							case 6:{
								pkt_l.prepend("<div class='dir' data='"+MTypeToString(MType)+"' ><i style='color:#f7d429' class='fa fa-bolt'></i></div>");
								pkt_l.addClass("msg_jr");
								up++;
								break;
							}
						}


						MHDRObj.MType = MTypeToString(MType);
						MHDRObj.RFU = ( MHDR & 0x1C ) >> 2;
						MHDRObj.Major = MHDR & 0x03;

						var PHYPLObj = {
							"MHDR" : MHDRObj
						};

						switch(MType){
							case 0:{
								PHYPLObj.JoinRequest = {};
								PHYPLObj.JoinRequest.AppEUI = "";
								for(var i = 7; i >= 0; i--){
									var si = PHYPayload.getUint8(i+1).toString(16).toUpperCase();;
									if( si.length < 2 )
										si ="0" + si;
									PHYPLObj.JoinRequest.AppEUI += si + " ";
								}

								PHYPLObj.JoinRequest.DevEUI = "";
								for(var i = 7; i >= 0; i--){
									var si = PHYPayload.getUint8(i+9).toString(16).toUpperCase();
									if( si.length < 2 )
										si ="0" + si;
									PHYPLObj.JoinRequest.DevEUI += si + " ";
								}
								PHYPLObj.JoinRequest.DevNonce = PHYPayload.getUint16(17, true).toString(16).toUpperCase();
								pkt_l.append("<div class='appeui' ><span class='pkt_l_i'>AppEUI</span><span>"+PHYPLObj.JoinRequest.AppEUI+"</span></div>");
								pkt_l.append("<div class='deveui' ><span class='pkt_l_i'>DevEUI</span><span>"+PHYPLObj.JoinRequest.DevEUI+"</span></div>");
								PHYPLObj.MIC = PHYPayload.getUint32(19, true).toString(16).toUpperCase();

								pkt_l.children(".rssi").first().html(info.rssi);
								pkt_l.children(".snr").first().html(info.lsnr);
								break;
							}
							case 1:{
								var JoinAccept = "";
								var len = info.size - 1 - 4;
								for( var i = 0; i < len; i++ ){
									var oneB = PHYPayload.getUint8(i+1);
									if( oneB <= 0x0F ){
										JoinAccept += "0"+oneB.toString(16).toUpperCase();
									}
									else{
										JoinAccept += oneB.toString(16).toUpperCase();
									}
								}
								PHYPLObj.JoinAccept = JoinAccept;
								PHYPLObj.MIC = PHYPayload.getUint32(info.size - 4).toString(16).toUpperCase();
								pkt_l.children(".txpwr").first().html(info.powe);
								break;
							}
							case 3:;
							case 5:;
							case 2:;
							case 4:{

								var FHDR = {};
								var MACPayload = {
									"FHDR" : FHDR
								};

								PHYPLObj.MACPayload = MACPayload;

								FHDR.DevAddr = PHYPayload.getUint32(1, true).toString(16).toUpperCase();

								while(FHDR.DevAddr.length < 8 ){
									FHDR.DevAddr = "0" + FHDR.DevAddr;
								}

								var fctrl = PHYPayload.getUint8(5);

								FHDR.FCtrl = {};
								FHDR.FCtrl.ADR = (fctrl & 0x80)?true:false;
								if( MType == 2 || MType == 4 ){
									FHDR.FCtrl.ADRACKReq = (fctrl & 0x40)?true:false;
									FHDR.FCtrl.ClassB = (fctrl & 0x10)?true:false;
								}
								else{
									FHDR.FCtrl.RFU = (fctrl & 0x40)?1:0;
									FHDR.FCtrl.FPending = (fctrl & 0x10)?true:false;
								}
								FHDR.FCtrl.ACK = (fctrl & 0x20)?true:false;
								
								FHDR.FCtrl.FOptsLen = fctrl & 0x0F;

								FHDR.FCnt = PHYPayload.getUint16(6, true);
								
								if( FHDR.FCtrl.FOptsLen > 0 ){
									//var FOptsLen = FHDR.FCtrl.FOptsLen;
									FHDR.FOpts = [];
									var CMDPtr = 8;
									while((CMDPtr - 8) < FHDR.FCtrl.FOptsLen){
										var CID = PHYPayload.getUint8(CMDPtr++);
										switch(CID){
											case 0x01 : {
												break;
											}
											case 0x02 :{
												if( MTYPE_UNCONFIRM_DATA_UP == MType ||
													MTYPE_CONFIRM_DATA_UP == MType
												){
													var LinkCheckReq = {
														CID : "LinkCheckReq"
													}
													FHDR.FOpts.push(LinkCheckReq);
													
												}
												else{
													var LinkCheckAns = {
														CID : "LinkCheckAns",
														Margin : PHYPayload.getUint8(CMDPtr++),
														GwCnt : PHYPayload.getUint8(CMDPtr++)
													}
													FHDR.FOpts.push(LinkCheckAns);
												}
												break;
											}
											case 0x03:
											{
												if( MTYPE_CONFIRM_DATA_DOWN == MType ||
													MTYPE_UNCONFIRM_DATA_DOWN == MType){
													var DRTxPwr = PHYPayload.getUint8(CMDPtr++);
													var ChMask = ((PHYPayload.getUint8(CMDPtr++) << 8) | PHYPayload.getUint8(CMDPtr++)).toString(16);
													while(ChMask.length < 4){
														ChMask = "0" + ChMask;
													}
													var Redundancy = PHYPayload.getUint8(CMDPtr++);

													var LinkADRReq = {
														CID : "LinkADRReq",
														DataRate : DRTxPwr >> 4,
														TXPower : DRTxPwr & 0x0F,
														ChMask : "0x" + ChMask,
														ChMaskCntl : (Redundancy & 0x70) >> 4,
														NbTrans :  Redundancy & 0x0F
													};
													FHDR.FOpts.push(LinkADRReq);
												}
												else{
													var ADRStatus = PHYPayload.getUint8(CMDPtr++);
													var LinkADRAns = {
														CID : "LinkADRAns",
														PowerACK : (ADRStatus & ( 0x01 << 2)) == 0 ? false : true,
														DataRateACK : ( ADRStatus & ( 0x01 << 1)) == 0 ? false : true,
														ChanMaskACK : ( ADRStatus & 0x01 ) == 0 ? false : true
													};
													FHDR.FOpts.push(LinkADRAns);
												}
												break;
											}
											case 0x04 :
											if(MType == MTYPE_UNCONFIRM_DATA_DOWN || MType ==MTYPE_CONFIRM_DATA_DOWN){
												var DytyCyclePL = PHYPayload.getUint8(CMDPtr++);

												var DutyCycleReq = {
													CID : "DutyCycleReq",
													MaxDCycle : (100.0 / 2^(DytyCyclePL & 0x0F)).toFixed(2) + "%"
												}
												FHDR.FOpts.push(DutyCycleReq);
											}else{
												FHDR.FOpts.push({
													CID : "DutyCycleAns"
												});
											}
											break;
											case 0x05 :
											if( MType == MTYPE_CONFIRM_DATA_DOWN || MType == MTYPE_UNCONFIRM_DATA_DOWN ){
												var DLSettings = PHYPayload.getUint8(CMDPtr++);
												var RX2Frequency = ( PHYPayload.getUint8(CMDPtr++) )|( PHYPayload.getUint8(CMDPtr++) << 8 )|(PHYPayload.getUint8(CMDPtr++) << 16);
												var RXParamSetupReq = {
													CID : "RXParamSetupReq",
													RX1DRoffset : (DLSettings & 0x70) >> 4,
													RX2DataRate : DLSettings & 0x0F,
													RX2Frequency : RX2Frequency * 100
												}
												FHDR.FOpts.push(RXParamSetupReq);
											}
											else{
												var RXParamStatus = PHYPayload.getUint8(CMDPtr++);
												var RXParamSetupAns = {
													CID : "RXParamSetupAns",
													RX1DRoffsetACK : (RXParamStatus & ( 0x01 << 2)) == 0 ? false : true,
													RX2DataRateACK : (RXParamStatus & ( 0x01 << 1)) == 0 ? false : true,
													ChannelACK : (RXParamStatus & ( 0x01 << 0)) == 0 ? false : true,
												}
												FHDR.FOpts.push(RXParamSetupAns);

											}
											break;
											case 0x06 : 
											if( MType == MTYPE_CONFIRM_DATA_DOWN || MType == MTYPE_UNCONFIRM_DATA_DOWN ){
												var DevStatusReq = {
													CID : "DevStatusReq"
												};

												FHDR.FOpts.push(DevStatusReq);
											}
											else{
												var Battery = PHYPayload.getUint8(CMDPtr++);
												var Margin = PHYPayload.getUint8(CMDPtr++);
												var DevStatusAns = {
													CID : "DevStatusAns",
													Battery : Battery,
													Margin : Margin
												};
												FHDR.FOpts.push(DevStatusAns);
											}
											break;
											case 0x07 :
											if( MType == MTYPE_CONFIRM_DATA_DOWN || MType == MTYPE_UNCONFIRM_DATA_DOWN ){
												var ChIndex = PHYPayload.getUint8(CMDPtr++);
												var Frequency =  ( PHYPayload.getUint8(CMDPtr++) )|( PHYPayload.getUint8(CMDPtr++) << 8 )|(PHYPayload.getUint8(CMDPtr++) << 16);
												var DrRange = PHYPayload.getUint8(CMDPtr++);
												var NewChannelReq = {
													CID : "NewChannelReq",
													ChIndex : ChIndex,
													Frequency : Frequency * 100,
													DrRange : {
														MaxDR : DrRange >> 4,
														MinDR : DrRange & 0x0F
													}
												}
												FHDR.FOpts.push(NewChannelReq);
											}
											else{
												var NewChannelStatus = PHYPayload.getUint8(CMDPtr++);
												var NewChannelAns = {
													CID : "NewChannelAns",
													DataRateRangeOK : (NewChannelStatus & ( 0x01 << 1)) == 0 ? false : true,
													ChanFrequencyOK : (NewChannelStatus & 0x01 ) == 0 ? false : true
												}
												FHDR.FOpts.push(NewChannelAns);
											}
											break;
											case 0x0A :
											if( MType == MTYPE_CONFIRM_DATA_DOWN || MType == MTYPE_UNCONFIRM_DATA_DOWN ){
												var DlChannelReq = {
													CID : "DlChannelReq",
													ChIndex : PHYPayload.getUint8(CMDPtr++),
													Frequency : ( ( PHYPayload.getUint8(CMDPtr++) )|( PHYPayload.getUint8(CMDPtr++) << 8 )|(PHYPayload.getUint8(CMDPtr++) << 16))*100
												}
												FHDR.FOpts.push(DlChannelReq);

											}
											else{
												var DlChannelStatus = PHYPayload.getUint8(CMDPtr++);
												var DlChannelAns = {
													CID : "DlChannelAns",
													UplinkFrequencyExists : (DlChannelStatus & ( 0x01 << 1 ) ) == 0 ? false : true,
													ChannelFrequencyOK :  (DlChannelStatus & ( 0x01 << 0 ) ) == 0 ? false : true
												}
												FHDR.FOpts.push(DlChannelReq);
											}
											break;
											case 0x08 :
											if( MType == MTYPE_CONFIRM_DATA_DOWN || MType == MTYPE_UNCONFIRM_DATA_DOWN ){
												var RXTimingSetupSettings = PHYPayload.getUint8(CMDPtr++);
												var RXTimingSetupReq = {
													CID : "RXTimingSetupReq",
													Delay : RXTimingSetupSettings == 0 ? 1 : RXTimingSetupSettings
												}
												FHDR.FOpts.push(RXTimingSetupReq);
											}
											else{
												FHDR.FOpts.push({
													CID : "RXTimingSetupAns"
												});
											}
											break;
											case 0x09:
											if( MType == MTYPE_CONFIRM_DATA_DOWN || MType == MTYPE_UNCONFIRM_DATA_DOWN ){
												var MaxEIRPTable = [8,10,12,13,14,16,18,20,21,24,26,27,29,30,33,36]
												var EIRP_DwellTime = PHYPayload.getUint8(CMDPtr++);
												var TxParamSetupReq = {
													CID : "TxParamSetupReq",
													DownlinkDwellTime : (EIRP_DwellTime & ( 0x01 << 5)) ? "400ms" : "No Limit",
													UplinkDwellTime : (EIRP_DwellTime & ( 0x01 << 4)) ? "400ms" : "No Limit",
													MaxEIRP : MaxEIRPTable[EIRP_DwellTime & 0x0F] + "dBm"
												}
												FHDR.FOpts.push(TxParamSetupReq);
											}
											else{
												FHDR.FOpts.push({
													CID : "TxParamSetupAns"
												});
											}
											break;
											case 0x0D :
											if( MType == MTYPE_CONFIRM_DATA_DOWN || MType == MTYPE_UNCONFIRM_DATA_DOWN ){
												var Seconds = (PHYPayload.getUint8(CMDPtr++) << 0 ) | (PHYPayload.getUint8(CMDPtr++) << 8) | (PHYPayload.getUint8(CMDPtr++) << 16) | (PHYPayload.getUint8(CMDPtr++)<<24);
												var fractionalSeconds = PHYPayload.getUint8(CMDPtr++);

												var DeviceTimeAns = {
													CID : "DeviceTimeAns",
													SecondsSinseEpoch : Seconds,
													fractionalSeconds : fractionalSeconds * ( 1.0 / (2^8))
												}
												FHDR.FOpts.push(DeviceTimeAns);
											}
											else{
												FHDR.FOpts.push({
													CID : "DeviceTimeReq"
												});
											}
											break;
											default:break;
										}
									}
								}
								pkt_l.children(".cnt").first().html(FHDR.FCnt);
								pkt_l.children(".devaddr").first().html(FHDR.DevAddr);
								if( MoteList[FHDR.DevAddr] == undefined ){
									MoteList[FHDR.DevAddr] = {
										airtime : 0,
										packet : 0
									};
								}


								if( info.rssi ){
									MoteList[FHDR.DevAddr].rssi = info.rssi;
									MoteList[FHDR.DevAddr].snr = info.lsnr;
								}

								if(airTime){
									MoteList[FHDR.DevAddr].airtime += airTime;
									MoteList[FHDR.DevAddr].packet ++;
								}
								

								if( info.size - 1 - 4 - 1 -2 - FHDR.FCtrl.FOptsLen - 4 > 0 ){
									MACPayload.FPort = PHYPayload.getUint8(8 + FHDR.FCtrl.FOptsLen);
									pkt_l.children(".fport").first().html(MACPayload.FPort);
									var FRMPayloadSize = info.size - 1 - 4 -1 -2-FHDR.FCtrl.FOptsLen - 4 -1;
									MACPayload.FRMPayload = "";
									for(var i = 0; i < FRMPayloadSize; i++ ){
										var byte = PHYPayload.getUint8(8 + FHDR.FCtrl.FOptsLen + i).toString(16);
										if(byte.length == 1){
											byte = "0" + byte;
										}
										MACPayload.FRMPayload += byte.toUpperCase() + " ";
									}
									pkt_l.children(".plsize").first().html(FRMPayloadSize);
									pkt_l.append("<div class='payload' ><span class='pkt_l_i'>Payload</span><span>"+MACPayload.FRMPayload+"</span></div>");
								}
								else{
									pkt_l.children(".plsize").first().html(0);
								}
								PHYPLObj.MIC = PHYPayload.getUint32( info.size - 4).toString(16).toUpperCase();
								if(MType == 2 || MType == 4 ){
									pkt_l.children(".rssi").first().html(info.rssi);
									pkt_l.children(".snr").first().html(info.lsnr);
								}
								else{
									pkt_l.children(".txpwr").first().html(info.powe);
								}
								if( FHDR.FOpts && FHDR.FOpts.length > 0 ){
									pkt_l.children(".cmd").first().html("");
									FHDR.FOpts.forEach(function(v,i){
										pkt_l.children(".cmd").first().html(pkt_l.children(".cmd").first().html() + v.CID + " ");
									})
								}
								break;
							}
						}

						$("#"+ pkt_id+"_d").append("<pre>"+JSON.stringify(PHYPLObj,null,4)+"</pre>");
					}
					if( fType == 1 ){
						pkt_l.append("<div class='ftype_info'>Push into buffer</div>")
					}
					else if( fType == 2 ){
						pkt_l.append("<div class='ftype_info'>Pop from buffer</div>")
					}
					else if( fType == 3){
						pkt_l.append("<div class='ftype_info'>TOO EARLY</div>")
					}
					else if( fType == 4){
						pkt_l.append("<div class='ftype_info'>TOO LATE</div>")
					}
					else if( fType == 5){
						pkt_l.append("<div class='ftype_info'>RADIO BUSY</div>")
					}
					pkt_filter(pkt_l);
					$("#pkt_total").html(total);
					$("#pkt_uplink").html(up);
					$("#pkt_downlink").html(down);
					
			});
			H337Refresh(currenttmms);
	});
}

function pkt_filter(pkt){
	var ftype = $("#filter_type").val();
	var fdevaddr = $("#filter_devaddr").val();
	var fcrc = $("#filter_crc").get(0).checked;

	var isShow = true;
	switch(ftype){
		case "All" :{
			isShow = true;
			break;
		}
		case "JoinAccept" : {
			if( pkt.children('.dir').first().attr("data") == "Join Accept"){
				isShow = true;
			}
			else{
				isShow = false;
			}
			break;
		}
		case "JoinRequest" : {
			if( pkt.children('.dir').first().attr("data") == "Join Request"){
				isShow = true;
			}
			else{
				isShow = false;
			}
			break;
		}
		case "UnconfirmedUp" :{
			if( pkt.children('.dir').first().attr("data") == "Unconfirmed Data Up"){
				isShow = true;
			}
			else{
				isShow = false;
			}
			break;
		}
		case "UnconfirmedDown" :{
			if( pkt.children('.dir').first().attr("data") == "Unconfirmed Data Down"){
				isShow = true;
			}
			else{
				isShow = false;
			}
			break;
		}
		case "ConfirmedUp" :{
			
			if( pkt.children('.dir').first().attr("data") == "Confirmed Data Up"){
				isShow = true;
			}
			else{
				isShow = false;
			}
			break;
		}
		case "ConfirmedDown" :{
			if( pkt.children('.dir').first().attr("data") == "Confirmed Data Down"){
				isShow = true;
			}
			else{
				isShow = false;
			}
			break;
		}
		default:
		isShow = false;
		break
	}
	if( isShow == false ){
		pkt.hide();
		return;
	}

	if( fdevaddr != "" ){
		var addrNode = pkt.children('.devaddr').get(0);
		
		if( addrNode == undefined ){
			pkt.hide();
			return;
		}

		var addr = $(addrNode).get(0).innerHTML;
		if( addr != fdevaddr ){
			pkt.hide();
			return;
		}
	}

	if( fcrc && pkt.children('.stat').get(0).innerHTML == 'CRC_ERR'){
		pkt.hide();
		return;
	}

	pkt.show();
}

$(document).ready(function(){
	var interval = setInterval(logread, 2000);
	var pause = false;
	$("#bt_pause").click(function(){
		if(pause == false){
			$("#p_i").removeClass("fa fa-pause");
			$("#p_i").addClass("fa fa-play");
			$("#p_t").html("Play");
			clearInterval(interval);
			pause = true;
		}
		else{
			$("#p_i").removeClass("fa fa-play");
			$("#p_i").addClass("fa fa-pause");
			$("#p_t").html("Pause");
			interval = setInterval(logread, 1000);
			pause = false;
		}

	});
	$('#bt_clr').click(function(){
		$("#packet_logger_div").html("");
		total = 0;
		up = 0;
		down = 0;
		$("#pkt_total").html(total);
		$("#pkt_uplink").html(up);
		$("#pkt_downlink").html(down);
	});

	$("#bt_dl").click(function(){
		
		var content = "";
		var header = $("#pkt_logge_div_header").children();
		for( var i = 0; i < header.length; i++ ){
			content += header[i].innerHTML;
			if( i != header.length - 1){
				content +=','
			}
		}
		content +='\r\n';
		var items = $(".pkt_l")
		
		for(var i = 0; i < items.length; i++ ){
			var item = $(items[i]).children();
			for(var j = 0; j < item.length; j++ ){
				if( j == 0 ){
					content += item[j].getAttribute('data');
				}
				else if(j==8 || j == 11){
					content += "'" + item[j].innerHTML+"'";
				}
				else if( j == 15 ){
					var cs = $(item[j]).children();
					if( cs.length > 1){
						content += cs[1].innerHTML;
					}
				}
				else
				{
					content += item[j].innerHTML;
				}
				
				if( j != item.length - 1){
					content += ',';
				}
			}
			content += "\r\n";
		}
		var aLink = document.createElement('a');
		var blob = new Blob([content],{type: "application/octet-stream"});


		aLink.download = "Packet_log" + new Date().format("_yyyyMMdd_hhmmss") + ".csv";
		aLink.style.display = "none";
		aLink.href = URL.createObjectURL(blob);
		document.body.appendChild(aLink);

		aLink.click();
		document.body.removeChild(aLink);
		
	});
	
	$("#filter_devaddr,#filter_crc,#filter_type").change(function(){
		var pkts = $(".pkt_l");
		$(".pkt_d").hide();
		pkts.each(function(i,v){
			pkt_filter($(v));
		});
	});
	
});
</script>

<h2><a id="content" name="content"><%:LoRaWAN Packet Logger%></a></h2>
<style type="text/css">
#pkt_logge_div_header{
	width:100%;
}

#packet_logger_div{
	display: block;
	width: 100%;

	height: 700px;
	overflow-y:scroll;
	overflow-x:scroll;
	border: 1px solid #eee;
}
#packet_logger_table td,
#packet_logger_table th{
	white-space: nowrap;
}
#hinfo{
	margin-top:8px;
	margin-bottom:8px;
	float: left;
}
#tinfo{
	margin-top:8px;
	margin-bottom:8px;
	float: right;
}
#tinfo > span,
#tinfo > a{
	padding-left: 12px;
	padding-left: 12px;
	width: 90px;
	display: block;
	float: right;
}
#hinfo > span{
	padding-right:  12px;
}
#packet_logger_div{
	display: block;
	width:100%;
	font-family: lato,helvetica,-apple-system,sans-serif;
}
#packet_logger_div .pkt_item{
	width: 100%;
	float:left;
}
#packet_logger_div .pkt_item .pkt_l{
	padding: 10px;
	width:150%;
	float:left;
	border-bottom: 1px solid #f0f0f0;
}

#packet_logger_div .pkt_item .pkt_l:hover{
	background: #f9f9f9;
}

#packet_logger_div .pkt_item .pkt_d{
	float:left;
	width:100%;
	display: none;
	padding:4px 16px;
	background: #f9f9f9;
}

#packet_logger_div .pkt_item .pkt_d > pre{
	border: 0;
	width: 50%;
	float: left;
	margin: 0;
	padding: 0;
	background: transparent;
}
#pkt_logge_div_header{
	width: 100%;
	display: block;
	float: left;
	padding: 4px 12px;
}
#pkt_logge_div_header > div {
	font-size:14px;
	font-weight: bold;
	display: block;
	float:left;
	white-space: nowrap;
	padding:4px 8px;
	text-align: center;
}
#packet_logger_div .pkt_item .pkt_l > div{
	display: block;
	float:left;
	text-align: center;
	padding: 4px 8px;
	white-space: nowrap;

}


#pkt_logge_div_header .dir,
#packet_logger_div .pkt_item .pkt_l .dir{
	width:4px;
}

#pkt_logge_div_header .tm,
#packet_logger_div .pkt_item .pkt_l .tm{
	width:60px;
}
#pkt_logge_div_header .stat,
#packet_logger_div .pkt_item .pkt_l .stat
{
	width:70px;
}
#pkt_logge_div_header .datr,
#packet_logger_div .pkt_item .pkt_l .datr{
	width:85px;
}

#pkt_logge_div_header .freq,
#packet_logger_div .pkt_item .pkt_l .freq{
	width:40px;
}

#pkt_logge_div_header .rssi,
#packet_logger_div .pkt_item .pkt_l .rssi{
	width:40px;
}
#pkt_logge_div_header .snr,
#packet_logger_div .pkt_item .pkt_l .snr{
	width:40px;
}
#pkt_logge_div_header .txpwr,
#packet_logger_div .pkt_item .pkt_l .txpwr{
	width:40px;
}
#pkt_logge_div_header .modu,
#packet_logger_div .pkt_item .pkt_l .modu{
	width:40px;
}
#pkt_logge_div_header .codr,
#packet_logger_div .pkt_item .pkt_l .codr{
	width:24px;
}
#pkt_logge_div_header .cnt,
#packet_logger_div .pkt_item .pkt_l .cnt{
	width:60px;
}
#pkt_logge_div_header .airtime,
#packet_logger_div .pkt_item .pkt_l .airtime{
	width:60px;
}

#pkt_logge_div_header .devaddr,
#packet_logger_div .pkt_item .pkt_l .devaddr{
	width:64px;
}

#pkt_logge_div_header .fport,
#packet_logger_div .pkt_item .pkt_l .fport{
	width:34px;
}
#pkt_logge_div_header .plsize,
#packet_logger_div .pkt_item .pkt_l .plsize{
	width:98px;
}
#packet_logger_div .pkt_item .pkt_l .payload{
	display:none;
}
.pkt_l_i{
	color: #828282;
	padding-right:8px;
}
#pkt_logger_filter{
	display: block;
	width: 100%;
	float: left;
}
#pkt_logger_filter > div{
	display: block;
	float: left;
	margin-bottom: 8px;
	padding-right: 12px;
}
#dutycycleht,
#cntht{
	width:50%;
	height:400px;
	float:left;
}
div.pkt_l.fType_1{
	background: #f1d5d4;
}
div.pkt_l.fType_2{
	background: #d4f1d9;
}
div.pkt_l.fType_3{
	background: #ff904f;
}
div.pkt_l.fType_4{
	background: #daa749;
}
div.pkt_l.fType_5{
	background: #fd9f9f;
}
#packet_logger_div div.pkt_l>div.ftype_info{
    text-align: right;
    width: 120px;
    color: #777;
    font-style: italic;
}
</style>

<fieldset class="cbi-section">
	<legend><%:LoRaWAN Packet Logger%></legend>
	<div id="pkt_logger_filter">
		<div id="type">
			<label for="">Type</label>
			<select id="filter_type">
				<option value="All">All</option>
				<option value="JoinRequest">Join Request</option>
				<option value="JoinAccept">Join Accept</option>
				<option value="UnconfirmedUp">Unconfirmed Data Up</option>
				<option value="UnconfirmedDown">Unconfirmed Data Down</option>
				<option value="ConfirmedUp">Confirmed Data Up</option>
				<option value="ConfirmedDown">Confirmed Data Down</option>
			</select>
		</div>
		<div>
			<label for="">DevAddr</label>
			<input id="filter_devaddr" type="text">
		</div>
		<div>
			<label for="filter_crc">Hide CRC_ERR packet</label>
			<input type="checkbox" id="filter_crc">
		</div>
	</div>
	<div id="hinfo">
		<span class="hinfo"><%:Total%> : <span id="pkt_total">0</span></span>
		<span  class="hinfo"><%:Uplink%> : <span id="pkt_uplink">0</span></span>
		<span  class="hinfo"><%:Downlink%> : <span id="pkt_downlink">0</span></span>
	</div>
	<div id="tinfo">
		<span id="bt_dl"><i class="fa fa-download"></i>&nbsp;&nbsp;<a><%:Download%></a></span>
		<span id="bt_clr"><i class="fa fa-trash"></i>&nbsp;&nbsp;<a><%:Clear%></a></span>
		<span id="bt_pause"><i class="fa fa-pause" id="p_i"></i>&nbsp;&nbsp;<a id="p_t" style="cursor: pointer;"><%:Pause%></a></span>
		
	</div>
	
	<div id="pkt_logge_div_header" class="pkt_logger">
		<div class="dir">&nbsp;</div>
		<div class="tm">Time</div>
		<div class="freq">Freq.</div>
		<div class="rssi">RSSI</div>
		<div class="snr">SNR</div>
		<div class="txpwr">TxPwr</div>
		<div class="stat">CRC</div>
		<div class="modu">mod.</div>
		<div class="codr">CR</div>
		<div class="datr">DataRate</div>
		<div class="cnt">FCnt</div>
		<div class="airtime">AirTime</div>
		<div class="devaddr">DevAddr</div>
		<div class="fport">FPort</div>
		<div class="plsize" >Payload Size</div>
		<div class="cmd">MAC Command</div>
	</div>
	<div id="packet_logger_div" class="pkt_logger">
		
	</div>
	<div style="position: relative;height:460px;width: 100%;overflow: hidden;background: #333">
		<div id="heatmap-bd" style="width: 680px;height:440px">
		</div>
		<div id="heatmap" style="width: 640px;height:400px">
		</div>
		<div id="heatmap-dt" >
			<div id="heatmap-dtc" style="height:100%;width: 100%"></div>
		</div>
		<div id="moteMap" style="width: 680px;height:440px">
		</div>
	</div>
</fieldset>
<%+footer%>
